package org.nutz.dao.impl.entity;

import java.sql.ResultSet;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.nutz.dao.FieldMatcher;
import org.nutz.dao.entity.Entity;
import org.nutz.dao.entity.EntityIndex;
import org.nutz.dao.entity.LinkField;
import org.nutz.dao.entity.LinkVisitor;
import org.nutz.dao.entity.MappingField;
import org.nutz.dao.entity.PkType;
import org.nutz.dao.sql.Pojo;
import org.nutz.lang.Lang;
import org.nutz.lang.Mirror;
import org.nutz.lang.born.BornContext;
import org.nutz.lang.born.Borning;
import org.nutz.lang.born.Borns;
import org.nutz.lang.util.Context;

/**
 * 记录一个实体
 * 
 * @author zozoh(zozohtnt@gmail.com)
 */
public class NutEntity<T> implements Entity<T> {

	/**
	 * 按照 Java 字段名索引映射字段
	 */
	private Map<String, MappingField> byJava;

	/**
	 * 按照数据库字段名索引映射字段
	 */
	private Map<String, MappingField> byDB;

	/**
	 * 按照增加顺序，记录所有映射字段
	 */
	private List<MappingField> fields;

	/**
	 * 按照顺序存储实体所有的索引
	 */
	private List<EntityIndex> indexes;

	/**
	 * 实体索引按照名称方式的散列
	 */
	private Map<String, EntityIndex> indexMap;

	/**
	 * 按顺序记录复合主键
	 */
	private List<MappingField> theComposites;

	/**
	 * 预执行宏列表
	 */
	private List<Pojo> beforeInsertMacroes;

	/**
	 * 后执行字段宏列表
	 */
	private List<Pojo> afterInsertMacroes;

	/**
	 * 所有一对一映射字段
	 */
	private LinkFieldSet ones;

	/**
	 * 所有一对多映射字段
	 */
	private LinkFieldSet manys;

	/**
	 * 所有多对多映射字段
	 */
	private LinkFieldSet manymanys;

	/**
	 * 数字型主键
	 */
	private MappingField theId;

	/**
	 * 字符型主键
	 */
	private MappingField theName;

	/**
	 * 实体 Java 类型
	 */
	private Class<T> type;

	/**
	 * 实体 Java 类型：通过 Mirror 增强
	 */
	private Mirror<T> mirror;

	/**
	 * 根据 ResultSet 创建实体的方法
	 */
	private Borning<T> bornByRS;

	/**
	 * 根据默认构造函数或者工厂方法创建实体的方法
	 */
	private Borning<T> bornByDefault;

	/**
	 * 实体表名
	 */
	private EntityName tableName;

	/**
	 * 实体视图名
	 */
	private EntityName viewName;

	/**
	 * 实体补充描述
	 */
	private Map<String, Object> metas;

	/**
	 * 实体的主键类型
	 */
	private PkType pkType;

	public NutEntity(Class<T> type) {
		this.type = type;
		this.mirror = Mirror.me(type);
		this.byJava = new HashMap<String, MappingField>();
		this.byDB = new HashMap<String, MappingField>();
		this.indexMap = new HashMap<String, EntityIndex>();
		this.fields = new ArrayList<MappingField>(5);
		this.indexes = new ArrayList<EntityIndex>(3);
		this.theComposites = new ArrayList<MappingField>(3);
		this.metas = new HashMap<String, Object>();

		this.pkType = PkType.UNKNOWN;

		// 字段宏
		beforeInsertMacroes = new ArrayList<Pojo>(3);
		afterInsertMacroes = new ArrayList<Pojo>(3);

		// 检查对象的创建方法
		BornContext<T> bc = Borns.evalByArgTypes(type, ResultSet.class);
		if (null == bc)
			this.bornByDefault = this.mirror.getBorningByArgTypes();
		else
			this.bornByRS = bc.getBorning();

		// 映射
		this.ones = new LinkFieldSet();
		this.manys = new LinkFieldSet();
		this.manymanys = new LinkFieldSet();
	}

	public T getObject(ResultSet rs, FieldMatcher matcher) {
		// 构造时创建对象
		if (null != bornByRS)
			return bornByRS.born(Lang.array(rs));

		// 通过反射每个字段逐次设置对象
		T re = bornByDefault.born(new Object[]{});
		if (null == matcher)
			for (MappingField fld : fields)
				fld.injectValue(re, rs);
		else
			for (MappingField fld : fields)
				if (matcher.match(fld.getName()))
					fld.injectValue(re, rs);

		// 返回构造的对象
		return re;
	}

	/**
	 * 当所有字段增加完成，这个方法必须被调用，用来搜索复合主键
	 * 
	 * @param names
	 *            复合主键的 Java 字段名数组
	 */
	public void checkCompositeFields(String[] names) {
		if (!Lang.isEmptyArray(names) && names.length > 1) {
			for (String name : names) {
				if (byJava.containsKey(name) && byJava.get(name).isCompositePk())
					theComposites.add(byJava.get(name));
				else
					throw Lang.makeThrow(	"Fail to find comosite field '%s' in class '%s'!",
											name,
											type.getName());
			}
			this.pkType = PkType.COMPOSITE;
		} else if (null != this.theId) {
			this.pkType = PkType.ID;
		} else if (null != this.theName) {
			this.pkType = PkType.NAME;
		}
	}

	/**
	 * 增加映射字段
	 * 
	 * @param field
	 *            数据库实体字段
	 */
	public void addMappingField(MappingField field) {
		if (field.isId())
			theId = field;
		else if (field.isName())
			theName = field;
		byJava.put(field.getName(), field);
		byDB.put(field.getColumnName(), field);
		fields.add(field);
	}

	/**
	 * 
	 * @param lnk
	 */
	public void addLinkField(LinkField lnk) {
		switch (lnk.getLinkType()) {
		case ONE:
			ones.add(lnk);
			break;
		case MANY:
			manys.add(lnk);
			break;
		case MANYMANY:
			manymanys.add(lnk);
			break;
		default:
			throw Lang.makeThrow(	"It is a miracle in Link field: '%s'(%s)",
									lnk.getName(),
									lnk.getEntity().getType().getName());
		}
	}

	/**
	 * 增加实体索引
	 * 
	 * @param index
	 *            实体索引
	 */
	public void addIndex(EntityIndex index) {
		indexes.add(index);
		indexMap.put(index.getName(), index);
	}

	public Context wrapAsContext(Object obj) {
		return new EntityObjectContext(this, obj);
	}

	public List<LinkField> visitOne(Object obj, String regex, LinkVisitor visitor) {
		return ones.visit(obj, regex, visitor);
	}

	public List<LinkField> visitMany(Object obj, String regex, LinkVisitor visitor) {
		return manys.visit(obj, regex, visitor);
	}

	public List<LinkField> visitManyMany(Object obj, String regex, LinkVisitor visitor) {
		return manymanys.visit(obj, regex, visitor);
	}

	public void setTableName(String namep) {
		this.tableName = EntityName.create(namep);
	}

	public void setBeforeInsertMacroes(List<Pojo> beforeInsertMacroes) {
		this.beforeInsertMacroes = beforeInsertMacroes;
	}

	public void setAfterInsertMacroes(List<Pojo> afterInsertMacroes) {
		this.afterInsertMacroes = afterInsertMacroes;
	}

	public void setViewName(String namep) {
		this.viewName = EntityName.create(namep);
	}

	public MappingField getField(String name) {
		return byJava.get(name);
	}

	public MappingField getColumn(String name) {
		return byDB.get(name);
	}

	public List<MappingField> getMappingFields() {
		return fields;
	}

	public List<LinkField> getLinkFields(String regex) {
		List<LinkField> reOnes = ones.getList(regex);
		List<LinkField> reManys = manys.getList(regex);
		List<LinkField> reManymanys = manymanys.getList(regex);
		List<LinkField> re = new ArrayList<LinkField>(reOnes.size()
														+ reManys.size()
														+ reManymanys.size());
		re.addAll(reOnes);
		re.addAll(reManys);
		re.addAll(reManymanys);
		return re;
	}

	public List<MappingField> getCompositePKFields() {
		return this.theComposites;
	}

	public MappingField getNameField() {
		return this.theName;
	}

	public MappingField getIdField() {
		return this.theId;
	}

	public List<MappingField> getPks() {
		if (null != theId)
			return Lang.list(theId);
		if (null != theName)
			return Lang.list(theName);
		return theComposites;
	}

	public Class<T> getType() {
		return this.type;
	}

	public Mirror<T> getMirror() {
		return this.mirror;
	}

	public List<EntityIndex> getIndexes() {
		return this.indexes;
	}

	public EntityIndex getIndex(String name) {
		return this.indexMap.get(name);
	}

	public String getTableName() {
		return this.tableName.value();
	}

	public String getViewName() {
		return this.viewName.value();
	}

	public boolean addBeforeInsertMacro(Pojo pojo) {
		if (null != pojo) {
			beforeInsertMacroes.add(pojo);
			return true;
		}
		return false;
	}

	public boolean addAfterInsertMacro(Pojo pojo) {
		if (null != pojo) {
			afterInsertMacroes.add(pojo);
			return true;
		}
		return false;
	}

	public List<Pojo> cloneBeforeInsertMacroes() {
		List<Pojo> re = new ArrayList<Pojo>(beforeInsertMacroes.size());
		for (Pojo pojo : beforeInsertMacroes)
			re.add(pojo.duplicate());
		return re;
	}

	public List<Pojo> cloneAfterInsertMacroes() {
		List<Pojo> re = new ArrayList<Pojo>(afterInsertMacroes.size());
		for (Pojo pojo : afterInsertMacroes)
			re.add(pojo.duplicate());
		return re;
	}

	public PkType getPkType() {
		return pkType;
	}

	public Object getMeta(String key) {
		return metas.get(key);
	}

	public boolean hasMeta(String key) {
		return metas.containsKey(key);
	}

	public Map<String, Object> getMetas() {
		return metas;
	}

	public String toString() {
		return String.format("Entity<%s:%s>", getType().getName(), getTableName());
	}

}
